/**
 * 
 */
package org.vsg.common.configration;

import java.io.File;
import java.util.Properties;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;

/**
 * @author Bill Vison
 * 
 */
public class PropertiesPlaceHolder extends PropertyResourceConfigurer implements
		InitializingBean, BeanNameAware, BeanFactoryAware {

	public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

	public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

	private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;

	private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;

	/** Never check system properties. */
	public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;

	/**
	 * Check system properties if not resolvable in the specified properties.
	 * This is the default.
	 */
	public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;

	/**
	 * Check system properties first, before trying the specified properties.
	 * This allows system properties to override any other property source.
	 */
	public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
	
	protected int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;	
	
	private String configPath = "system:config.path";

	private boolean searchSystemEnvironment = true;	
	
	private BeanFactory beanFactory;

	private String beanName;

	/**
	 * defined configuration properties
	 */
	protected Configuration config;

	/**
	 * set the logger for develope code , do not use System.print(ln) to out print message.
	 */
	private static final Logger logger = LoggerFactory.getLogger(PropertiesPlaceHolder.class);	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.config.PropertyResourceConfigurer#processProperties(org.springframework.beans.factory.config.ConfigurableListableBeanFactory,
	 *      java.util.Properties)
	 */
	@Override
	protected void processProperties(
			ConfigurableListableBeanFactory beanFactoryToProcess,
			Properties props) throws BeansException {
		// TODO Auto-generated method stub
		BeanDefinitionVisitor visitor = new PlaceholderResolvingBeanDefinitionVisitor(
				props);
		String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();

		for (int i = 0; i < beanNames.length; i++) {
			if (!(beanNames[i].equals(this.beanName) && beanFactoryToProcess
					.equals(this.beanFactory))) {
				BeanDefinition bd = beanFactoryToProcess
						.getBeanDefinition(beanNames[i]);

				try {
					visitor.visitBeanDefinition(bd);
				} catch (BeanDefinitionStoreException ex) {
					throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanNames[i], ex.getMessage());
				}
			}

		}
	}

	protected String resolvePlaceholder(String placeholder, Properties props) {
		String propVal = null;
	
		propVal = this.config.getString(placeholder);
		return propVal;
	}

	protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
		String propVal = null;
		
		if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
			propVal = resolveSystemProperty(placeholder);
		}
		if (propVal == null) {
			propVal = props.getProperty(placeholder);
		}
		if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
			propVal = resolveSystemProperty(placeholder);
		}

		return propVal;
	}	
	
	
	protected String parseStringValue(String strVal, Properties props,
			String originalPlaceholder) throws BeanDefinitionStoreException {

		StringBuffer buf = new StringBuffer(strVal);

		// The following code does not use JDK 1.4's
		// StringBuffer.indexOf(String)
		// method to retain JDK 1.3 compatibility. The slight loss in
		// performance
		// is not really relevant, as this code will typically just run on
		// startup.

		int startIndex = strVal.indexOf(this.placeholderPrefix);

		while (startIndex != -1) {
			int endIndex = buf.toString().indexOf(this.placeholderSuffix,
					startIndex + this.placeholderPrefix.length());
			if (endIndex != -1) {
				String placeholder = buf.substring(startIndex
						+ this.placeholderPrefix.length(), endIndex);

				String originalPlaceholderToUse = null;

				if (originalPlaceholder != null) {
					originalPlaceholderToUse = originalPlaceholder;
					if (placeholder.equals(originalPlaceholder)) {
						throw new BeanDefinitionStoreException(
								"Circular placeholder reference '"
										+ placeholder
										+ "' in property definitions");
					}
				} else {
					originalPlaceholderToUse = placeholder;
				}
				// -------------- custom rewrite : read data from system environment

				String propVal = this.resolvePlaceholder(placeholder, props);
				
				// -------------- custom rewrite : read the local configration file where the environment
				
				if (propVal == null) {
					propVal = resolvePlaceholder(placeholder, props, this.systemPropertiesMode);;
				}
				
				
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in
					// the
					// previously resolved placeholder value.
					propVal = parseStringValue(propVal, props,
							originalPlaceholderToUse);
					buf.replace(startIndex, endIndex
							+ this.placeholderSuffix.length(), propVal);
					startIndex = buf.toString().indexOf(this.placeholderPrefix,
							startIndex + propVal.length());
				} else {
					throw new BeanDefinitionStoreException(
							"Could not resolve placeholder '" + placeholder
									+ "'");
				}

			}

			else {
				startIndex = -1;
			}
		}

		return buf.toString();
	}

	public void setConfigPath(String path) {
		configPath = path;
	}

	public String getConfigPath() {
		return configPath;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		String path = null;
		if (configPath != null) {
			if (configPath.startsWith("system:")) {
				path = configPath.split(":")[1];
			}

			else {
				throw new Exception("Did not support property path");
			}
		}

		// load System path
		String systemPath = null;
		systemPath = System.getProperty(path);
		
		if (systemPath == null) {
			throw new Exception("Not defined System Property \"config.path\"!Please setting by using \"java -Dconfig.path=XXXX\" .");
		}

		if (!systemPath.endsWith(File.separator)) {
			systemPath = systemPath + File.separator;
		}
		
		logger.debug("Loading System Properties Path [" + systemPath + "]");
		
		// check path exist
		File sysPath = new File(systemPath);
		if (!sysPath.exists()) {
			throw new Exception("System Folder [\""+systemPath+"\"] does not exist! Please check it!");
		}

		String systemConfFile = systemPath + "system.xml";

		sysPath = new File(systemPath);
		if (!sysPath.exists()) {
			throw new Exception("System Folder [\""+systemPath+"\"] does not exist! Please check it!");
		}		
		

		// load config using apache configartion
		ConfigurationFactory factory = new ConfigurationFactory();
		factory.setConfigurationFileName(systemConfFile);

		config = factory.getConfiguration();

	}

	/**
	 * Resolve the given key as JVM system property, and optionally also as
	 * system environment variable if no matching system property has been found.
	 * @param key the placeholder to resolve as system property key
	 * @return the system property value, or <code>null</code> if not found
	 * @see #setSearchSystemEnvironment
	 * @see java.lang.System#getProperty(String)
	 * @see java.lang.System#getenv(String)
	 */
	protected String resolveSystemProperty(String key) {
		try {
			String value = System.getProperty(key);
			if (value == null && this.searchSystemEnvironment) {
				value = System.getenv(key);
			}
			return value;
		}
		catch (Throwable ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Could not access system property '" + key + "': " + ex);
			}
			return null;
		}
	}	
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
	 */
	public void setBeanName(String beanName) {
		// TODO Auto-generated method stub
		this.beanName = beanName;

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
	 */
	public void setBeanFactory(BeanFactory factory) throws BeansException {
		// TODO Auto-generated method stub
		this.beanFactory = factory;
	}

	/**
	 * BeanDefinitionVisitor that resolves placeholders in String values,
	 * deleagating to the <code>parseStringValue</code> method of the
	 * containing clas.
	 */
	private class PlaceholderResolvingBeanDefinitionVisitor extends
			BeanDefinitionVisitor {

		private final Properties props;

		public PlaceholderResolvingBeanDefinitionVisitor(Properties props) {
			this.props = props;
		}

		protected String resolveStringValue(String strVal)
				throws BeansException {
			return parseStringValue(strVal, this.props, null);
		}
	}

	
	
}
